package org.flexthinker.binding.form.support
{
    import mx.collections.ArrayCollection;

    import mx.collections.IList;

    import org.flexthinker.binding.value.support.BufferedCollectionValueModel;
    import org.flexthinker.binding.value.support.ListValueChangeDetector;
    import org.flexthinker.binding.value.support.MockBean;

    import org.flexthinker.binding.value.support.ValueHolder;
    import org.hamcrest.assertThat;
    import org.hamcrest.core.not;
    import org.hamcrest.number.lessThan;
    import org.hamcrest.object.equalTo;

    public class DefaultFormModelTest
    {
        private var fm:DefaultFormModel;
        private var bean:MockBean;

        public function DefaultFormModelTest()
        {
        }

        [Before]
        public function setUp():void
        {
            bean = new MockBean();
            bean.bindableProperty = "SOME CLEAN PROP";
        }

        [Ignore(message="Enable this only when you want to test performance")]
        [Test]
        public function testFormModelProcessingPerformance():void
        {
            fm = new DefaultFormModel(bean, true);
            var startTime:uint = new Date().getUTCMilliseconds();

            fm.add("bindableProperty");

            for (var i:int = 0; i < 100000; i++)
            {
                fm.getValueModel("bindableProperty").setValue("iteration" + i);
            }
            // at consecutive runs, the time should be around 15 ms, but at first run it usualy takes about 300-600ms
            assertThat("WARNING ! FormModel Performance may not be good anymore",
                    (new Date().getUTCMilliseconds() - startTime), lessThan(600));
        }

        [Test]
        public function testFormBecomesDirtyWhenValueModelUpdates():void
        {
            fm = new DefaultFormModel(bean, true);
            var eventListener:EventListenerCounter = new EventListenerCounter(fm, DefaultFormModel.DIRTY_STATE_CHANGED);

            assertThat("form shouldn't be dirty initally", fm.isDirty(), equalTo(false));
            assertThat("dirty state shouldn't change initially", eventListener.counter, equalTo(0));

            fm.add("bindableProperty");
            assertThat("form shouldn't be dirty after adding a new property", fm.isDirty(), equalTo(false));
            assertThat("dirty state shouldn't change after adding a new property", eventListener.counter, equalTo(0));

            bean.bindableProperty = "SOME NEW CLEAN PROP";
            assertThat("form shouldn't become dirty when domainObject updates", fm.isDirty(), equalTo(false));
            assertThat("formModel should reflect changed in domainObject",
                    fm.getValueModel("bindableProperty").getValue(), equalTo(bean.bindableProperty));
            assertThat("dirty state shouldn't change when domain object changes", eventListener.counter, equalTo(0));

            fm.getValueModel("bindableProperty").setValue("SOME DIRTY PROP");
            assertThat("formModel should become dirty when changing properties", fm.isDirty(), equalTo(true));
            assertThat("dirty state should fire a dirtyStateChanged event", eventListener.counter, equalTo(1));
        }

        [Test]
        public function testFormModelBecomesCleanWhenFormObjectUpdates():void
        {
            fm = new DefaultFormModel(bean, true);
            var eventListener:EventListenerCounter = new EventListenerCounter(fm, DefaultFormModel.DIRTY_STATE_CHANGED);

            fm.add("bindableProperty");
            bean.bindableProperty = "SOME NEW CLEAN PROP";
            assertThat("form shouldn't become dirty when domainObject updates", fm.isDirty(), equalTo(false));
            assertThat("formModel should reflect changed in domainObject",
                    fm.getValueModel("bindableProperty").getValue(), equalTo(bean.bindableProperty));
            assertThat("dirty state shouldn't change when domain object changes", eventListener.counter, equalTo(0));

            fm.getValueModel("bindableProperty").setValue("SOME DIRTY PROP");
            assertThat("formModel should become dirty when changing properties", fm.isDirty(), equalTo(true));
            assertThat("dirty state should fire a dirtyStateChanged event", eventListener.counter, equalTo(1));

            bean.bindableProperty = "SOME OTHER CLEAN PROP";
            assertThat("formModel should become clean when domainObject changes", fm.isDirty(), equalTo(false));
            assertThat("dirty state should fire a dirtyStateChanged event", eventListener.counter, equalTo(2));
        }

        [Test]
        public function testFormModelRevert():void
        {
            fm = new DefaultFormModel(bean, true);
            fm.add("bindableProperty");

            var origVal:String = fm.getValueModel("bindableProperty").getValue().toString();

            fm.getValueModel("bindableProperty").setValue("SOME DIRTY PROP");
            assertThat("formModel should become dirty when changing properties", fm.isDirty(), equalTo(true));

            fm.revert();
            assertThat("formModel should be clean after revert()", fm.isDirty(), equalTo(false));
            assertThat("ValueModel should be identical with the formObject",
                    fm.getValueModel("bindableProperty").getValue(), equalTo(bean.bindableProperty));
            assertThat("ValueModel should revert too", fm.getValueModel("bindableProperty").getValue(),
                    equalTo(origVal));
        }

        [Test]
        public function testFormModelBecomesCleanAfterCommit():void
        {
            fm = new DefaultFormModel(bean, true);
            fm.add("bindableProperty");
            fm.add("simpleBoolean");
            fm.add("simpleString");


            bean.bindableProperty = "P1";
            bean.simpleBoolean = false;
            bean.simpleString = "unchanged";

            assertThat("FormModel should NOT be dirty", fm.isDirty(), equalTo(false));
            fm.getValueModel("simpleBoolean").setValue(true);

            assertThat("FormModel should be dirty", fm.isDirty(), equalTo(true));
            fm.getValueModel("bindableProperty").setValue("NEW_P");

            assertThat("FormModel should be dirty", fm.isDirty(), equalTo(true));

            fm.commit();
            assertThat("FormModel should NOT be dirty after commit", fm.isDirty(), equalTo(false));

            assertThat("FormModel didn't set the correct Boolean value", bean.simpleBoolean, equalTo(true));
            assertThat("FormModel didn't set the correct String value", bean.bindableProperty, equalTo("NEW_P"));
            assertThat("FormModel didn't set the correct simpleString value", bean.simpleString, equalTo("unchanged"));
        }

        [Test]
        public function testFormModelBecomesDirtyWhenListChanges():void
        {
            fm = new DefaultFormModel(bean, true);
            fm.add("simpleBoolean");
            fm.add("simpleString");

            bean.simpleList = new ArrayCollection([1,2,3]);
            fm.addValueModelFor("simpleList",
                    new BufferedCollectionValueModel(new ValueHolder(bean, "simpleList"), ArrayCollection));

            assertThat("FormModel should NOT be dirty", fm.isDirty(), equalTo(false));

            var ac:ArrayCollection = fm.getValueModel("simpleList").getValue() as ArrayCollection;

            assertThat("ValueModel's List shouldn't have same instance with bean's List", ac,
                    not(equalTo(bean.simpleList)));

            IList(fm.getValueModel("simpleList").getValue()).addItem(5);
            assertThat("FormModel should become dirty when a watched list changes", fm.isDirty(), equalTo(true));

            fm.revert();
            assertThat("FormModel should clean after revert", fm.isDirty(), equalTo(false));
            assertThat("List should be reverted", fm.getValueModel("simpleList").getValue().length, equalTo(3));

            IList(fm.getValueModel("simpleList").getValue()).addItem(5);
            fm.commit();
            assertThat("list committed into bean shouldn't have same ref", bean.simpleList,
                    not(equalTo(fm.getValueModel("simpleList").getValue())));
            assertThat("bean's collection and formModel collection should have same items",
                    new ListValueChangeDetector().hasValueChanged(bean.simpleList,
                            fm.getValueModel("simpleList").getValue()), equalTo(false));

            assertThat("form model should be clean after commit", fm.isDirty(), equalTo(false));


        }
    }
}